package com.googlecode.skald.client.affordances;

import java.util.List;

/**
 * Represents a game-world action that could be performed by the player's 
 * current character.  'PC' stands for 'Player Character'.
 * <p>
 * Every PcAction is built from a list of display strings specified by the 
 * JSON object from the server.  This must be a list of at least one String
 * (such as an intransitive verb, like "Look" or "Wait"). 
 * <p>
 * <code>cmd</code> is also provided by the server. If cmd is null, it will 
 * default to a space-separated concatenation of the display strings.
 * The cmd String may contain variables $1, $2, ... $9.  These will be 
 * replaced with the values of the display elements at the corresponding
 * indices, up to a max of the number of display elements.  
 * 
 * @author Zach Tomaszewski
 * @since 05 Nov 2012
 * @version 23 Feb 2013
 */
public class PcAction implements Affordance {
  
  private String[] display;
  private String cmd;
  private String cmdText;
  private boolean weak;
  private int order;

  /**
   * Constructs an affordance with the given display elements and command.
   * The affordance will be strong (not weak) with an order of 0.
   * 
   * @see #PcAction(List, String, int, boolean)
   */
  public PcAction(List<String> displayElems, String cmd) 
                  throws InvalidFormatException{
    this(displayElems, cmd, 0, false);
  }    
  
  /**
   * Constructs an affordance with the given order, display elements, command,
   * and weakness.
   * <p>
   * Hint: java.util.Arrays.toList("verb", "dobj") work well to get a quick
   * List<String> for the first parameter.
   * <p>
   * Does not currently enforce that display element components are non-null.
   * 
   * @throws InvalidFormatException  if displayElems doesn't include at least
   *                                 one element. 
   */
  public PcAction(List<String> displayElems, String cmd, int order, boolean isWeak) 
                  throws InvalidFormatException{
    this.order = order;
    if (displayElems == null || displayElems.size() == 0) {
      throw new InvalidFormatException("No display elements given:" + 
                                        displayElems);
    }
    this.display = displayElems.toArray(new String[displayElems.size()]);
    this.cmd = cmd;
    this.cmdText = null;  //lazy init
    this.weak = isWeak;
  }

  /** 
   * Returns the raw cmd String.  This is the value sent from the server.
   * If none was sent, will be null.  If not null, it will still include any 
   * $variable markers that will need to be replaced to produce 
   * {@link #getCommandText()}.
   */
  public String getCmd() {
    return this.cmd;
  }


  @Override
  public String getCommandText() {
    if (this.cmdText == null) {
      if (this.cmd == null) {
        //construct: space-separated display elements
        StringBuilder str = new StringBuilder();
        for (int i = 0; i < this.display.length; i++) {
          str.append(this.display[i]);
          if (i < this.display.length - 1) {
            str.append(" ");
          }
        }
        this.cmdText = str.toString();
      }else {
        //variable replacement
        this.cmdText = cmd;
        int max = Math.min(display.length, 9);
        for (int i = 0; i < max; i++) {
          cmdText = cmdText.replace("$" + i, display[i]);
        }
      }
    }
    return this.cmdText;
  }

  @Override
  public String[] getDisplayElements() {
    return this.display;  //NOTE: could violate immutability
  }

  /**
   * Returns the display element at the given index.
   * 
   * @throws IndexOutOfBoundsException
   */
  public String getElement(int i) {
    return this.display[i];
  }

  @Override
  public int getOrder() {
    return order;
  }

  @Override
  public boolean isWeak() {
    return weak;
  }

  @Override
  public int compareTo(Affordance that) {
    int byOrder = this.getOrder() - that.getOrder();
    if (byOrder != 0) {
      return byOrder;
    }
    //order is same, so need to consider elements...
    
    String[] thisElems = this.getDisplayElements();
    String[] thatElems = that.getDisplayElements();

    int shortest = Math.min(thisElems.length, thatElems.length);
    int longest = Math.max(thisElems.length, thatElems.length); 
    
    // compare by elements, finding the first that differs from the other
    int byElems = 0;
    for (int i = 0; i < shortest; i++) {
      byElems = thisElems[i].compareTo(thatElems[i]);
      if (byElems != 0) {
        return byElems;
      }
    }    
    //equal all the way up through shortest...
    
    if (longest > shortest) {
      //figure out which is longer
      return (thisElems.length == longest) ? 1 : -1; 
    }
    
    assert (longest == shortest);  //and equal throughout (possibly empty)
    return this.getCommandText().compareTo(that.getCommandText());
  }

  /**
   * Two PcActions are equal if they have equal {@link #getOrder()},
   * {@link #getDisplayElements()}, and the same {@link #getCommandText()}. 
   */
  @Override
  public final boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (obj instanceof PcAction) {
      PcAction act = (PcAction) obj;
      return this.getOrder() == act.getOrder() && 
          this.getCommandText().equals(act.getCommandText()) &&
          java.util.Arrays.equals(this.getDisplayElements(), 
                                  act.getDisplayElements());
    }
    return false;
  }
  
  @Override
  public final int hashCode() {
    int hash = this.getCommandText().hashCode();
    hash += this.getOrder();
    hash += java.util.Arrays.hashCode(this.getDisplayElements());
    return hash;
  }

  @Override
  public String toString() {
    return "PcAction[affordance=" + java.util.Arrays.toString(this.display) + 
        ", cmd='" + this.getCommandText() + "'" +
        ", order=" + this.order +
        ((this.weak) ? ", weak" : "") + 
        "]";
  }
}
